/*  This file is part of -_-.

    -_- is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    -_- is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with -_-.  If not, see <http://www.gnu.org/licenses/>.

    Copyright 2012 Oliver Katz */

#include "backend_opengl.h"

namespace sleepyface
{
	namespace visual
	{
		/* I shamelessly stole this code. All credit goes
		 * to r3dux (r3dux.org). Here's the link to the
		 * post:
		 * http://r3dux.org/2010/11/single-call-opengl-texture-loader-in-devil
		 */
		GLuint ilutGLLoadImage(char *theFileName)
		{
			ILuint imageID;				// Create an image ID as a ULuint
		 
			GLuint textureID;			// Create a texture ID as a GLuint
		 
			ILboolean success;			// Create a flag to keep track of success/failure
		 
			ILenum error;				// Create a flag to keep track of the IL error state
		 
			ilGenImages(1, &imageID); 		// Generate the image ID
		 
			ilBindImage(imageID); 			// Bind the image
		 
			success = ilLoadImage(theFileName); 	// Load the image file
		 
			// If we managed to load the image, then we can start to do things with it...
			if (success)
			{
				// If the image is flipped (i.e. upside-down and mirrored, flip it the right way up!)
				ILinfo ImageInfo;
				iluGetImageInfo(&ImageInfo);
				if (ImageInfo.Origin == IL_ORIGIN_UPPER_LEFT)
				{
					iluFlipImage();
				}
		 
				// Convert the image into a suitable format to work with
				// NOTE: If your image contains alpha channel you can replace IL_RGB with IL_RGBA
				success = ilConvertImage(IL_RGB, IL_UNSIGNED_BYTE);
		 
				// Quit out if we failed the conversion
				if (!success)
				{
					error = ilGetError();
					std::cout << "error: ilut: " << error << " - " << iluErrorString(error) << "\n";
					exit(-1);
				}
		 
				// Generate a new texture
				glGenTextures(1, &textureID);
		 
				// Bind the texture to a name
				glBindTexture(GL_TEXTURE_2D, textureID);
		 
				// Set texture clamping method
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
		 
				// Set texture interpolation method to use linear interpolation (no MIPMAPS)
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		 
				// Specify the texture specification
				glTexImage2D(GL_TEXTURE_2D, 				// Type of texture
							 0,				// Pyramid level (for mip-mapping) - 0 is the top level
							 ilGetInteger(IL_IMAGE_BPP),	// Image colour depth
							 ilGetInteger(IL_IMAGE_WIDTH),	// Image width
							 ilGetInteger(IL_IMAGE_HEIGHT),	// Image height
							 0,				// Border width in pixels (can either be 1 or 0)
							 ilGetInteger(IL_IMAGE_FORMAT),	// Image format (i.e. RGB, RGBA, BGR etc.)
							 GL_UNSIGNED_BYTE,		// Image data type
							 ilGetData());			// The actual image data itself
		 	}
		  	else // If we failed to open the image file in the first place...
		  	{
				error = ilGetError();
				std::cout << "error: ilut: " << error << " - " << iluErrorString(error) << "\n";
		  	}
		 
		 	ilDeleteImages(1, &imageID); // Because we have already copied image data into texture data we can release memory used by image.

		 
			return textureID; // Return the GLuint to the texture so you can use it!
		}

		float Backend::hexcol2gl(int c)
		{
			return ((float)c)/((float)0xff);
		}

		float Backend::x2gl(int x)
		{
			return ((float)(x-width/2.0f))/((float)width);
		}

		float Backend::y2gl(int y)
		{
			return ((float)(y-height/2.0f))/((float)height);
		}

		Backend::Backend()
		{
			context = NULL;
		}

		int Backend::init(int *argc, char ***argv)
		{
			glutInit(argc, *argv);
			glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE);
			glutInitWindowSize(200, 200);

			ilInit();
			ilutRenderer(ILUT_OPENGL);
			ilutEnable(ILUT_OPENGL_CONV);

			glClearColor(0.0, 0.0, 0.0, 1.0);
			return 0;
		}

		int Backend::async()
		{
			glutCreateWindow(("-_- "+name).c_str());
		}

		int Backend::halt()
		{
			return 0;
		}

		string Backend::setName(string n)
		{
			return (name = n);
		}

		string Backend::getName()
		{
			return name;
		}

		int Backend::getWidth()
		{
			return width;
		}

		int Backend::setWidth(int w)
		{
			if (context == NULL)
				return (width = w);
			else
				return -1;
		}

		int Backend::getHeight()
		{
			return height;
		}

		int Backend::setHeight(int h)
		{
			if (context == NULL)
				return (height = h);
			else
				return -1;
		}

		int Backend::rect(int x0, int y0, int x1, int y1, int r, int g, int b, int a)
		{
			glColor3f(hexcol2gl(r), hexcol2gl(g), hexcol2gl(b));
			glRectf(x2gl(x0), y2gl(y0), x2gl(x1), y2gl(y1));
			return 0;
		}

		int Backend::line(int x0, int y0, int x1, int y1, int r, int g, int b, int a)
		{
			glColor3f(hexcol2gl(r), hexcol2gl(g), hexcol2gl(b));
			glBegin(GL_LINES);
			glVertex2f(x2gl(x0), y2gl(y0));
			glVertex2f(x2gl(x1), y2gl(y1));
			glEnd();
			return 0;
		}

		int Backend::imageGetSize(string path, int &w, int &h)
		{
			GLuint img = ilutGLLoadImage((char *)path.c_str());
			w = ilGetInteger(IL_IMAGE_WIDTH);
			h = ilGetInteger(IL_IMAGE_HEIGHT);
			ilDeleteImage(img);
			glDeleteTextures(1, &img);
			return 0;
		}

		int Backend::image(int x, int y, string path)
		{
			GLuint img = ilutGLLoadImage((char *)path.c_str());
			int w = ilGetInteger(IL_IMAGE_WIDTH);
			int h = ilGetInteger(IL_IMAGE_HEIGHT);
			glEnable(GL_TEXTURE_2D);
			glBindTexture(GL_TEXTURE_2D, img);
			glBegin(GL_QUADS);
			glTexCoord2f(x2gl(x), y2gl(y)); glVertex2f(x2gl(x), y2gl(y));
			glTexCoord2f(x2gl(x+w), y2gl(y)); glVertex2f(x2gl(x+w), y2gl(y));
			glTexCoord2f(x2gl(x+w), y2gl(y+h)); glVertex2f(x2gl(x+w), y2gl(y+h));
			glTexCoord2f(x2gl(x), y2gl(y+h)); glVertex2f(x2gl(x), y2gl(y+h));
			glEnd();
			ilDeleteImage(img);
			glDeleteTextures(1, &img);
			return 0;
		}

		int Backend::loadFont(string name)
		{
			GLuint base = glGenLists(256);
			Display *display = XOpenDisplay(0);
			XFontStruct *fs = XLoadQueryFont(display, name.c_str());
			int first = fs->min_char_or_byte2;
			int last = fs->max_char_or_byte2;
			glXUseXFont(fs->fid, first, last-first+1, base+first);
			XCloseDisplay(display);
			fontdb[name] = base;
			return 0;
		}

		int Backend::loadFont(string name, string path, int size)
		{
			return loadFont(path);
		}

		int Backend::unloadFont(string name)
		{
			return 0;
		}

		int Backend::getTextWidth(string font, string text)
		{
			return 0;
		}

		int Backend::text(int x0, int y0, int r, int g, int b, int a, string font, string s)
		{
			glColor4f(hexcol2gl(r), hexcol2gl(g), hexcol2gl(b), hexcol2gl(a));
			glRasterPos2f(x0-width/2, y0-height/2);
			glPushAttrib(GL_LIST_BIT);
			glListBase(fontdb[font]);
			glCallLists(s.size(), GL_UNSIGNED_BYTE, (GLubyte *)s.c_str());
			return 0;
		}

		int Backend::update()
		{
			width = glutGet(GLUT_WINDOW_WIDTH);
			height = glutGet(GLUT_WINDOW_HEIGHT);
			glXSwapBuffers(display, window);
			return 0;
		}

		int Backend::bindDisplay(void (*cb)(void))
		{
			glutDisplayFunc(cb);
			return 0;
		}

		int Backend::bindKeyPress(void (*cb)(unsigned char k, int x, int y))
		{
			glutKeyboardFunc(cb);
			return 0;
		}

		int Backend::bindKeyRelease(void (*cb)(unsigned char k, int x, int y))
		{
			glutKeyboardUpFunc(cb);
			return 0;
		}

		int Backend::bindHover(void (*cb)(int x, int y))
		{
			glutPassiveMotionFunc(cb);
			return 0;
		}

		int Backend::bindDrag(void (*cb)(int x, int y))
		{
			glutMotionFunc(cb);
			return 0;
		}

		int Backend::bindButton(void (*cb)(int b, int s, int x, int y))
		{
			glutMouseFunc(cb);
			return 0;
		}

		Display *Backend::getX11Display()
		{
			return display;
		}

		Window Backend::getX11Window()
		{
			return window;
		}

		GLXContext Backend::getOpenGLContext()
		{
			return context;
		}

		extern "C"
		{
			Backend *_vb;
		}
	}
}
